// Item table view/editing (mainly useful for quick 'enterprise applications')
// Copyright (C) 2011-2012 Jani Kajala. Licensed under MIT-license.
// Dependencies: none (works also on browser!)

/**
 * Constructs EdiTable.
 * @param opt Options. See EdiTable.prototype.opt for defaults.
 */
function EdiTable( opt ) {
	if ( typeof opt.keys == 'undefined' )
		throw new Error( "EdiTable: opt.keys must be defined (types of keys/columns)" );

	// merge options to defaults
	var clone = function(o) {var c={}; if (typeof o==='undefined') return c; for(var k in o) c[k]=o[k]; return c;}
	var opt1 = clone(EdiTable.prototype.opt);
	opt1.types = clone(opt1.types);
	if ( typeof opt != 'undefined' ) {
		for (var k in opt)
		{
			switch (k)
			{
			case 'types': // deep copy
				opt1[k] = clone(opt1[k]);
				for(var type in opt.types)
					opt1[k][type] = opt[k][type];
				break;
			default: // shallow copy
				opt1[k] = opt[k];
				break;
			}
		}
	}
	this.opt = opt1;
}

/**
 * Exception thrown when input filtering fails.
 * @param msg Error message
 * @param label Invalid input value label
 */
EdiTable.prototype.InputError = function(msg,label) {
	this.msg = msg;
	this.label = label;
};

/** Returns input error as string "msg: label". */
EdiTable.prototype.InputError.prototype.toString = function() {
	return this.msg + ": " + this.label;
};

/**
 * Default options.
 */
EdiTable.prototype.opt = {
	/** 
	 * Object which contains keys/columns and their types as value. Must be defined.
	 * Built-in supported types: number, text, datetime, bool, email.
	 */
	keys: {},
	/** 
	 * Definitions of each type used by keys. Each type can contain: {
	 *  filter: Input filter function(value,label).
	 * 	format: Formatter function(value),
	 *  label: Label on column heading
	 * }
	 */
	types: {
		text: {
			filter:		function(v,label) 	{return v;},
			format:		function(v,label)	{return v;}
		},
		bool: {
			filter:		function(v,label) 	{var t=EdiTable.prototype.opt.types; v=t.prefilter(v).toLowerCase(); if (typeof t.boolValues[v] === 'undefined') throw new EdiTable.prototype.InputError("Invalid boolean",label); return t.boolValues[v];},
			format:		function(v,label)	{var t=EdiTable.prototype.opt.types; return v === true ? t.boolTrue : t.boolFalse;}
		},
		email: {
			filter:		function(v,label) 	{var t=EdiTable.prototype.opt.types; v=t.prefilter(v); if (!t.emailRegex.test(v)) throw new EdiTable.prototype.InputError("Invalid email",label); return v;},
			format:		function(v,label)	{return v;}
		},
		number: {
			filter:		function(v,label) 	{var t=EdiTable.prototype.opt.types; v=t.prefilter(v); v=parseFloat(v); if (isNaN(v)) throw new EdiTable.prototype.InputError("Not a number",label); return v;},
			format:		function(v,label)	{return v;}
		},
		datetime: {
			filter:		function(v,label)	{var t=EdiTable.prototype.opt.types; v=t.prefilter(v); var d=new Date(v); if (isNaN(d.getTime()) ) throw EdiTable.prototype.InputError("Invalid date",label); return d;},
			format:		function(v) 		{return typeof v.toISOString == 'function' ? v.toISOString() : v;}
		},
		
		// helpers
		prefilter: function(v) {v=''+v; v=v.replace(/^\s+/,''); v=v.replace(/\s+$/,''); return v;},
		boolValues: {'yes':true, 'true':true, 'enabled':true, '1':true, 'no':false, 'false':false, 'disabled':false, '0':false},
		boolTrue: 'true',
		boolFalse: 'false',
		emailRegex: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/
	},
	/** Data table column heading template. %s marks value and %a attributes from opt.types[key] object. */
	th: "<th %a>%s</th>",
	/** Data table cell template. %s marks value and %a attributes from opt.types[key] object. */
	td: "<td %a>%s</td>",
	/** Data table row template. %s marks content. */
	tr: " <tr>\n  %s\n </tr>\n",
	/** Data table template. %s marks content. */
	table: "<table>%s</table>"
};

/**
 * Formats value to string.
 * @param v Value
 * @param type Type name of data to be formatted. Used to index this.opt.types[type].format function.
 * @param key Name of the key/column name to be formatted.
 */
EdiTable.prototype.formatValue = function( v, type, label ) {
	type = ( typeof this.opt.types[type] != 'undefined' ? this.opt.types[type] : {} );
	if ( typeof type.format === 'function' )
		return type.format( v, label );
	return v;
};

/**
 * Returns object as table cell HTML.
 * @param v Value
 * @param k Column/key to be formatted.
 * @return HTML string
 */
EdiTable.prototype.td = function( v, k ) {
	var type = this.opt.keys[k];
	return this.opt.td.replace( '%s', this.formatValue(v,type,k) );
};

/**
 * Returns object as table column heading HTML.
 * @param label Label
 * @return HTML string
 */
EdiTable.prototype.th = function( label ) {
	return this.opt.th.replace('%s',label);
};

/**
 * Returns object/array as table row HTML.
 * @param a String or object of single row, with a[key] for each value matching column key.
 * @return HTML string
 */
EdiTable.prototype.tr = function( a ) {
	var o = a;
	if ( typeof a == 'object') {
		var keys = this.opt.keys;
		var types = this.opt.types;
		o = '';
		for (var k in keys) {
			var v = '';
			if ( typeof a[k] != 'undefined' )
				v = a[k];
			o += this.td(v,k);
		}
	}
	return this.opt.tr.replace('%s',o);
}

/** 
 * Formats table of data to HTML for edit/view.
 * 
 * @param items Array of objects to edit/view
 * @return HTML
 */
EdiTable.prototype.table = function( items ) {
	var keys = this.opt.keys;
	var types = this.opt.types;
	var o = '';
	for (var k in keys) {
		var type = keys[k];
		var label = (typeof types[type] != 'undefined' && typeof types[type].label != 'undefined' ? types[type].label : k);
		o += this.th(label);
	}
	o = this.tr(o);
	for (var i = 0 ; i < items.length ; ++i)
		o += this.tr( items[i] );
	return this.opt.table.replace('%s',o);
};

//------------------------------------------------------------------------------------------------
// module.exports
//------------------------------------------------------------------------------------------------

if ( typeof module != 'undefined' && typeof module.exports != 'undefined' ) {
	module.exports = EdiTable;
}
